/*
 * Copyright 2004 Stottler Henke Associates, Inc.
 * All rights reserved.
 */
package com.stottlerhenke.simbionic.util;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 * Takes a Java class and generates a SimBionic class specification
 * for it.  
 * Obsolete - the main method should call ClassSpecFromJarGenerator instead
 */
public class ClassSpecGenerator
{
    private static String _classList = new String();
    private static ArrayList _classes = new ArrayList();
    private static String _specPath;
    private static Class _theClass;
    private static boolean _append = false;
    
    private static void readParameters(String[] args)
    {
        if (args.length < 4)
        {
            System.out.println("Options:");
            System.out.println(" -c <class> path to the class to be analyzed");
            System.out.println(" -cl <file> file listing the classes to be analyzed");
            System.out.println(" -o <spec>  path for the generated specification file");
            System.out.println(" -a			appends the specification to the specified file");
            System.exit(-1);
        }
   
        int arg = 0;
        while (arg < args.length)
        {
            if (args[arg].equals("-c"))
            {
                _classes.add(new String(args[++arg]));
            }
            else if (args[arg].equals("-cl"))
            {
                _classList = args[++arg];
            }
            else if (args[arg].equals("-o"))
            {
                _specPath = args[++arg];
            }
            else if (args[arg].equals("-a"))
            {
                _append = true;
            }
            else
            {
                System.out.println("Unknown option '" + args[arg] + "'");
            }
            ++arg;
        }
    }
    
    private static void loadClassList()
    {
        try 
        {
            FileReader reader = new FileReader(_classList);
            
            char classList[] = new char[1000]; 
            if (reader.read(classList,0,1000) != -1)
            {
                StringTokenizer tokenizer = new StringTokenizer(new String(classList),"\n");
                while (tokenizer.hasMoreTokens())
                {
                    String className = tokenizer.nextToken();
                    className = className.trim();
                    if(!className.isEmpty())
                    	_classes.add(new String(className));
                }
            }
            
            reader.close();
        }
        catch (IOException exc)
        {
            System.out.println("Error reading class list.");
            System.exit(-1);      
        }
    }
    
    private static void loadClass(String className) throws Exception
    {
        ClassLoader loader = ClassLoader.getSystemClassLoader();

        try
        {
	        System.out.print("Attempting to load class " + className + "...");
	        _theClass = loader.loadClass(className);
	        System.out.println("loaded.");
        }
        catch (Exception e)
        {
          	//e.printStackTrace();
            throw new Exception("Can't load class " + className + " : " + e.toString());
        }
    }
    /*
    private static void loadJarClass()
    {
    	
        // Create the jar class loader and use the jar file to use. 
        JarClassLoader jarLoader = new JarClassLoader(_jarFile);
        // Load the class from the jar file and resolve it. 
        Class c = jarLoader.loadClass (args [1], true);
    }
    */
    private static void writeSpec(Writer writer,String className) throws IOException
    {
        writer.write("class " + className + "\r\n");
        
        writeFields(writer);
        writeConstructors(writer);
        writeMethods(writer);
         
        writer.write("endclass\r\n");
    }
    
    private static void writeFields(Writer writer) throws IOException
    {
        // write variables
        Field[] fields = _theClass.getFields();
        for (int i=0; i<fields.length; ++i)
        {
            Field field = fields[i];
            
            // only write out public fields
            int modifiers = field.getModifiers();
            if (!Modifier.isPublic(modifiers))
                continue;
            
            String name = field.getName();
            
            String type = field.getType().getName();
            
            // no fields with array types
            if (type.indexOf('[') != -1)
                continue;
            
            String isStatic = (Modifier.isStatic(modifiers) ? "static " : "");
            
            writer.write("\tvar " + isStatic + type + " " + name + "\r\n");
        }
    }
    
    private static void writeConstructors(Writer writer) throws IOException
    {
        // write constructors
        Constructor[] constructors = _theClass.getConstructors();
        for (int j=0; j<constructors.length; ++j)
        {
            String methodSpec = new String();
            
            Constructor method = constructors[j];
            
            // only write out public methods
            int modifiers = method.getModifiers();
            if (!Modifier.isPublic(modifiers))
                continue;
            
            // the name is fully qualified, so strip off the packages
            String name = method.getName();
            String shortName = name.substring(name.lastIndexOf('.')+1);

            // treat constructors as methods with return type of the class --
            // the engine can distinguish them by their names
            methodSpec += "\tmethod " + name + " " + shortName + "(";
            
            // write params
            boolean badParam = false;
            Class[] paramTypes = method.getParameterTypes();
            for (int p=0; p < paramTypes.length; ++p)
            {
                if (p != 0)
                    methodSpec += ",";
                
                String paramType = paramTypes[p].getName();
                if (paramType.indexOf('[') != -1)
                {
                    badParam = true;
                    break;	// no array param types
                }
                
                methodSpec += paramType + " p" + p;
            }
            
            if (badParam)
                continue;
            
            methodSpec += ")\r\n";
            writer.write(methodSpec);
        }
    }
    
    private static void writeMethods(Writer writer) throws IOException
    {
        // write methods
        Method[] methods = _theClass.getMethods();
        for (int j=0; j<methods.length; ++j)
        {
            String methodSpec = new String();
            
            Method method = methods[j];
            
            // only write out public methods
            int modifiers = method.getModifiers();
            if (!Modifier.isPublic(modifiers))
                continue;
            
            String name = method.getName();

            Class returnType = method.getReturnType();
            String retType = returnType.getName() + " ";
            if (retType.indexOf('[') != -1)
                continue; // no array return types!

            String isStatic = (Modifier.isStatic(modifiers) ? "static " : "");
            
            methodSpec += "\tmethod " + isStatic + retType + name + "(";
            
            // write params
            boolean badParam = false;
            Class[] paramTypes = method.getParameterTypes();
            for (int p=0; p < paramTypes.length; ++p)
            {
                if (p != 0)
                    methodSpec += ",";
                
                String paramType = paramTypes[p].getName();
                if (paramType.indexOf('[') != -1)
                {
                    badParam = true;
                    break;	// no array param types
                }
                
                methodSpec += paramType + " p" + p;
            }
            
            if (badParam)
                continue;
            
            methodSpec += ")\r\n";
            writer.write(methodSpec);
        }
    }

    private static void writeErrorLog(String message)
    {
        FileWriter fileWriter = null;
        try
        {
            File f = new File(_specPath);
            String path = f.getParent();
            fileWriter = new FileWriter(path+"\\auto_error.txt",false);
            fileWriter.write(message);
            fileWriter.close();
        }
        catch (IOException e)
        {
            System.out.println("Error writing error log.");
        }
    }
    
    public static void main(String[] args)
    {
        readParameters(args);

        if (!_classList.equals(""))
            loadClassList();

        // generate the class spec for all classes
        StringWriter writer = new StringWriter();
        try
        {
	        Iterator classIt = _classes.iterator();
	        while (classIt.hasNext())
	        {
	            String classToLoad = (String)classIt.next();
	            loadClass(classToLoad);
	            writeSpec(writer,classToLoad);
	        }            
        }
        catch (Exception e)
        {
            writeErrorLog(e.getMessage());
            System.out.println("\nError generating class spec: " + e.getMessage());
            System.exit(0);
        }
        	
        // write the spec out
        FileWriter fileWriter = null;
        try
        {
            fileWriter = new FileWriter(_specPath,_append);
            fileWriter.write(writer.toString());
	        fileWriter.close();
        }
        catch (IOException e)
        {
            System.out.println("Error writing class spec file: " + e.getMessage());
        }
    }
}
